/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.constraint_store;

import org.jmock.core.Constraint;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class HashMapConstraintStore implements ConstraintStore {
	private final Map<Object, Constraint> sentinelToConstraintMap =
		new HashMap<Object, Constraint>();

	private final SentinelSource sentinelSource;
	
	public HashMapConstraintStore() {
		this(new SentinelSource());
	}
	
	public HashMapConstraintStore(SentinelSource sentinelSource) {
		this.sentinelSource = sentinelSource;
	}
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// ConstraintStore methods
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	public int putInteger(Constraint integerConstraint) {
		int sentinel = sentinelSource.nextInt();

		sentinelToConstraintMap.put(sentinel, integerConstraint);

		return sentinel;
	}
	
	public long putLong(Constraint longConstraint) {
		long sentinel = sentinelSource.nextLong();

		sentinelToConstraintMap.put(sentinel, longConstraint);

		return sentinel;
	}
	
	public double putDouble(Constraint doubleConstraint) {
		double sentinel = sentinelSource.nextDouble();

		sentinelToConstraintMap.put(sentinel, doubleConstraint);

		return sentinel;
	}

	public String putString(Constraint constraint) {
		String sentinelValue = sentinelSource.nextString();

		sentinelToConstraintMap.put(sentinelValue, constraint);

		return sentinelValue;
	}

	public byte[] putByteArray(Constraint byteArrayConstraint) {
		byte[] byteArraySentinel = sentinelSource.nextByteArray();
		
		sentinelToConstraintMap.put(new ByteArrayWrapper(byteArraySentinel), byteArrayConstraint);
		
		return byteArraySentinel;
	}

	public static class ByteArrayWrapper {
		private final byte[] byteArray;

		public ByteArrayWrapper(byte[] byteArray) {
			this.byteArray = byteArray;
		}
		
		@Override
		public int hashCode() {
			return Arrays.hashCode(byteArray);
		}
		
		@Override
		public boolean equals(Object object) {
			if (object == null) {
				return false;
			}

			if (object == this) {
				return true;
			}
			
			if (!object.getClass().equals(getClass())) {
				return false;
			}
			
			ByteArrayWrapper other = (ByteArrayWrapper) object;
			
			return other.hasValue(byteArray);
		}

		public boolean hasValue(byte[] byteArray) {
			return Arrays.equals(this.byteArray, byteArray);
		}
	}

	public Constraint[] getConstraints(Constraint[] defaultConstraints, List parameters) {
		List<Constraint> constraints = new ArrayList<Constraint>(parameters.size());

		int index = 0;
		for (Object parameter : parameters) {
			constraints.add(getConstraint(defaultConstraints[index++], parameter));
		}

		return constraints.toArray(new Constraint[constraints.size()]);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Implementation
	///////////////////////////////////////////////////////////////////////////////////////////////

	private Constraint getConstraint(Constraint defaultConstraint, Object parameter) {
		if (parameter != null && parameter.getClass().isArray()) {
			parameter = wrapArray(parameter);
		}
		
		return sentinelToConstraintMap.containsKey(parameter)
			? sentinelToConstraintMap.get(parameter)
			: defaultConstraint; 
	}

	private Object wrapArray(Object parameter) {
		if (parameter.getClass().getComponentType().equals(byte.class)) {
			return new ByteArrayWrapper((byte[]) parameter);
		}
		else {
			return parameter;
		}
	}
}
